#案例---基于RFM的精细化用户管理
#导入模块
import time
import numpy as np
import pandas as pd
import pymysql
from sqlalchemy import create_engine

from sklearn.ensemble import RandomForestClassifier
from pyecharts.charts import Bar3D
import pyecharts.options as opts


data=pd.read_excel('sales.xlsx',sheet_name=None)
#获取每个表的名字
sheet_name_list = list(data.keys())

#对数据进行审查，前N条数据，以及数据描述信息的主要分布规律，缺失值，以及判断数据目标类型当前的状态，以及后续是否需要做特殊处理
for i in range(len(sheet_name_list)):
    print('[data summary for {0:^50}]'.format(sheet_name_list[i]))
    print('Overview:','\n',data[sheet_name_list[i]].head(4))
    print('DESC:','\n',data[sheet_name_list[i]].describe())
    print('NA records',data[sheet_name_list[i]].isnull().any(axis=1).sum())
    print('Dtypes',data[sheet_name_list[i]].dtypes)
    
'''
由上可以得出的结论:
1. 每个sheet表里面的数据都能够正常读取和识别，且没有任何错误
2.日期列已经被自动识别为日期格式，这省去了后期做转换的过程
3.订单金额的分布时不均匀的，里面有明显的极大值，例如2016年数据中，最大值为174900，最小值为0.1，这样的分布状态，数据会受到
极值的影响。
4.订单金额中的最小值包括0，0.1这样的金额，明显时不正常的订单。通过与业务方沟通后确认，最大值的订单金额有效，通常是客户一次性购买
多个大家电商品；而订单金额为0.1元这类使用优惠券支付的订单，没有实际意义。除此之外，所有低于1元的订单均有这个问题，因此需要后续处理中去掉。
dropna()去掉缺失值
过滤值大于1的记录数替换掉原来数据框的值
增加一列最大值日期，使用max进行获取
'''

# 去掉缺失值以及异常值,指针对订单数据，不包括会员表
a=[]
for k in range(len(sheet_name_list)-1):
   data[sheet_name_list[k]]=data[sheet_name_list[k]].dropna()
   data[sheet_name_list[k]]=data[sheet_name_list[k]][data[sheet_name_list[k]]['订单金额']>1]#去除订单金额<=1的记录
   data[sheet_name_list[k]]['max_year_date']=data[sheet_name_list[k]]['提交日期'].max()#增加一列最大值日期值
   #汇总所有的数据
   a.append(data[sheet_name_list[k]])
#汇总数据
data_mearge=pd.concat(a,axis=0)

#获取日期间隔并转化为数字
data_mearge['date_interval']=(data_mearge['max_year_date']-data_mearge['提交日期']).apply(lambda x:x.days)
#增加新字段，记录发生的年份
data_mearge['year']=data_mearge['提交日期'].dt.year

'''
1.基于年份和用户ID进行分类汇总---groupby,as_index=False意味着year和会员ID不作为index列，而是普通的数据框结果列
后面的agg是一个具有批量聚合功能的函数，实现了对date_interval、提交日期、订单金额三列分别以min、count、sum做聚合计算的功能，
否则，需要写三个groupby来实现。
2.r表示的是与截止日期之间的间隔，f指的是每个会员的订单频率，m指的是全部次数相加起来的总和。
3.从基本概要看出，r,m的数据分布相对比较离散，表现在min、25%、50%、75%和max的数据没有特别集中，
4. r/m可以很好的区分用户特征，但是f不行，因为大多数用户只有1个订单，计划选择25%和75%作为区间划分2个边界值。
'''
#按会员ID做汇总
rfm_ID=data_mearge.groupby(['year','会员ID'],as_index=False).agg({'date_interval':
    'min',#-------------计算最近一次订单时间
    '提交日期':'count',#-------计算订单频率
    '订单金额':'sum'})   #---------计算订单总额

#重命名列名
rfm_ID.columns=['year','会员ID','r','f','m']
 
#查看数据分布,只针对rfm三列查看
desc_pd=rfm_ID.iloc[:,2:].describe().T
print(desc_pd)

#定义区间边界依据只要以25%和75%进行划分，不能超过max的最大值
#针对f频率他是以认为购买2次或两次以上可被定义为复购用户，5次是业务部门认为普通用户
#购买的非常高的次数了。
r_bins=[-1,79,255,365]#注意起始边界小于最小值
f_bins=[0,2,5,130]
m_bins=[0,69,1199,206252]

'''
例如一组数据[1,2,3,4,5],划分数据为[1,3,5],
那么遵循左开右闭的原则，其中的2/3数据划分到(1,3]中，1取不到,
(3,5则为另一个区间
'''
#计算RFM的权重
#通过建立一个rfm三个维度与会员等级的分类模型，通过模型输出维度的权重。
#匹配会员等级与rfm的得分
#在这一步可以进行预测数据，。。。。。。。。
rfm_merge=pd.merge(rfm_ID,data['会员等级'],on='会员ID',how='inner')

#通过rf获取rfm得分
clf=RandomForestClassifier()
clf=clf.fit(rfm_merge[['r','f','m']],rfm_merge['会员等级'])
weights=clf.feature_importances_#获取重要特征
print('feature importance:',weights)
#从结果上面看，其偏向于订单钱的总数，其次是新近程度，最后是频次。

#计算RFM的过程
#RFM分箱得分,label用来显示，每个离散化后的具体值,减一是为了实现边界数量与区间数量的平衡
rfm_ID['r_score']=pd.cut(rfm_ID['r'],r_bins,labels=[i for i in range(len(r_bins)-1,0,-1)])
rfm_ID['f_score']=pd.cut(rfm_ID['f'],f_bins,labels=[i+1 for i in range(len(f_bins)-1)])
rfm_ID['m_score']=pd.cut(rfm_ID['m'],m_bins,labels=[i+1 for i in range(len(m_bins)-1)])

#计算总得分，加权的得分
rfm_ID=rfm_ID.apply(np.int32)#cate数值
rfm_ID['rfm_score']=rfm_ID['r_score']*weights[0]+rfm_ID['f_score']*weights[1]+rfm_ID['m_score']*weights[2]

rfm_ID['r_score']=rfm_ID['r_score'].astype(np.str)
rfm_ID['f_score']=rfm_ID['f_score'].astype(np.str)
rfm_ID['m_score']=rfm_ID['m_score'].astype(np.str)
rfm_ID['rfm_group']=rfm_ID['r_score'].str.cat(rfm_ID['f_score']).str.cat(rfm_ID['m_score'])



#将RFM结果保存到excel当中
#rfm_ID.to_excel('sales_rfm_score.xlsx')

#将数据写入数据库
#建立数据引擎
#db=create_engine('mysql+pymysql://root:123456@localhost:3306/mql?charset=utf8')
#rfm_ID.to_sql("RFMDATA",db,if_exists="replace")

#rgb图形展示
display_data=rfm_ID.groupby(['rfm_group','year'],as_index=False)['会员ID'].count()
display_data.columns=['rfm_group','year','number']
display_data['rfm_group']=display_data['rfm_group'].astype(np.int32)


#显示图形 
#bar3d=Bar3D()

range_color=['#313695','#4575b4','#74add1','#abd9e9','#e0f3f8','#ffffbf','#fee090','#fdae61',
             '#f46d43','#d73027','#d73027','#a50026']

#bar3d.add(
#        "rfm分组结果",
#        "",
#        "",
#        [d.tolist() for d in display_data.values],
#        is_visualmap=True,
#        visual_range=[0,display_data['number'].max()],
#        visual_range_color=range_color,
#    )
#
##保存为html格式
#bar3d.render_notebook()
bar=Bar3D(init_opts=opts.InitOpts(width="1600px", height="800px"))

bar.add(
        series_name="",
        data= [d.tolist() for d in display_data.values],
        xaxis3d_opts=opts.Axis3DOpts(type_="category"),
        yaxis3d_opts=opts.Axis3DOpts(type_="category"),
        zaxis3d_opts=opts.Axis3DOpts(type_="value"),
    )
bar.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            max_=20,
            range_color=range_color
            ))
bar.render("bar3d_punch_card.html")












##查看pyecharts的版本
#import pyecharts 
#print(pyecharts.__version__)
#
#
##生成图片
#from snapshot_selenium import snapshot as driver
#
#from pyecharts import options as opts
#from pyecharts.charts import Bar
#from pyecharts.render import make_snapshot
#
#
#def bar_chart() -> Bar:
#    c = (
#        Bar()
#        .add_xaxis(["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"])
#        .add_yaxis("商家A", [114, 55, 27, 101, 125, 27, 105])
#        .add_yaxis("商家B", [57, 134, 137, 129, 145, 60, 49])
#        .reversal_axis()
#        .set_series_opts(label_opts=opts.LabelOpts(position="right"))
#        .set_global_opts(title_opts=opts.TitleOpts(title="Bar-测试渲染图片"))
#    )
#    return c
#
## 需要安装 snapshot-selenium 或者 snapshot-phantomjs
#make_snapshot(driver, bar_chart().render(), "bar.png")
